/* ==============================================================================
*
*   Copyright (c) 2010, Pierre Favier
*   All rights reserved.
*
*   NTasking is free software: you can redistribute it and/or modify it
*   under the terms of the GNU General Public License as published by the
*   Free Software Foundation.
*
*   Redistribution and use in source and binary forms, with or without
*   modification, are permitted provided that the following conditions
*   are met:
*
*   -   Redistributions of source code must retain the above copyright notice,
*       this list of conditions and the following disclaimer.
*
*   -   Redistributions in binary form must reproduce the above
*       copyright notice, this list of conditions and the following
*       disclaimer in the documentation and/or other materials
*       provided with the distribution.
*
*   THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED
*   WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
*   OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
*   DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
*   BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
*   EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
*   TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
*   DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
*   ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
*   TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
*   THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
*   SUCH DAMAGE.
*
* ============================================================================== */

using System;
using System.Diagnostics;
using System.Reflection;
using System.Threading;
using NTasking.Internals;
using Void = NTasking.Internals.Void;

namespace NTasking
{

    /// <summary>
    /// The <see cref="Protected"/> class implements Ada's protected types
    /// </summary>
    public abstract partial class Protected : SynchronizedObject, IDisposable
    {

        #region Fields

        /// <summary>
        /// The lock used to implement the eggshell model for protected operations
        /// </summary>
        private readonly NamedReaderWriterLockSlim _protectedLock;

        #endregion


        #region Properties

        private bool Disposed { get; set; }


        /// <summary>
        /// Gets or sets the priority of this instance.
        /// </summary>
        /// <value>The priority of this instance.</value>
        public sealed override ThreadPriority Priority
        {
            get
            {
                return base.Priority;
            }
            set
            {
                base.Priority = value;
            }
        }


        /// <summary>
        /// Gets or sets the name of this instance.
        /// </summary>
        /// <value>The name of this instance.</value>
        public sealed override string Name
        {
            get
            {
                return base.Name;
            }
            set
            {
                base.Name = value;
            }
        }

        #endregion


        #region Constructors

        /// <summary>
        /// Initializes a new instance of the <see cref="Protected"/> class.
        /// </summary>
        protected Protected()
            : base(false)
        {
            Priority = ThreadPriority.Highest;
            _protectedLock = new NamedReaderWriterLockSlim("ProtectedEggshell", this);

            Master.AddDependent(this);
        }


        /// <summary>
        /// Releases unmanaged resources and performs other cleanup operations before the
        /// <see cref="Protected"/> is reclaimed by garbage collection.
        /// </summary>
        ~Protected()
        {
            Dispose(false);
        }

        #endregion


        #region Methods

        /// <summary>
        /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
        /// </summary>
        public void Dispose()
        {
            Dispose(true);
        }


        /// <summary>
        /// Releases unmanaged and - optionally - managed resources
        /// </summary>
        /// <param name="disposeManagedResources"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param>
        private void Dispose(bool disposeManagedResources)
        {
            bool disposeUnmanagedResources = true;

            if (Disposed)
                return;

            // Dispose managed resources
            if (disposeManagedResources)
            {
                disposeUnmanagedResources = ProtectedAction("DISPOSE", true, false, false, () =>
                {
                    if (Disposed)
                        return false;

                    Tracer.Trace("Disposing " + this);

                    BroadcastProgramError(Errors.ProtectedIsBeingDisposed(this));

                    if (Master != null)
                        Master.RemoveDependent(this);

                    Master = null;

                    Disposed = true;

                    return true;
                });

                GC.SuppressFinalize(this);
            }

            // Dispose unmanaged resources
            if (disposeUnmanagedResources && _protectedLock != null)
                _protectedLock.Dispose();
        }


        /// <summary>
        /// Perform a protected operation
        /// </summary>
        /// <typeparam name="RetT">The type of the protected operation's result.</typeparam>
        /// <param name="call">The protected method call.</param>
        /// <param name="body">The body of the protected operation.</param>
        /// <returns>The result of the protected operation</returns>
        private RetT ProtectedOperationCall<RetT>(SynchronizedMethod call, Func<RetT> body)
        {
            bool internalCall = ThreadWrapper.CurrentThread.InternalCall(this);

            // Make sure that the caller's current priority does not exceed the ceiling priority
            if (Task.CurrentTask.CurrentPriority > Priority)
                throw Errors.PriorityCeilingExceeded(Task.CurrentTask, call);

            // Prevent External calls during a protected action
            if (!internalCall && ThreadWrapper.CurrentThread.RecursiveCall(this))
                throw Errors.PotentiallyBlockingOperationNotAllowed("External call from protected action");

            // Prevent read-write calls while a read-only protected action is in progress
            if (ThreadWrapper.CurrentThread.ReadOnlyOperationInProgress && !call.IsReadOnly)
                throw Errors.InvalidReadWriteCall(this, call, ThreadWrapper.CurrentThread.CallInProgress);

            if (!call.HasBody)
                throw Errors.UninitializedProtectedOperation(call);

            try
            {
                if (internalCall)
                    return ExecuteProtectedOperationCall(call, body);

                string name = call.IsReadOnly ? "READ-ONLY" : "READ-WRITE" + " CALL ";

                return ProtectedAction(name, call.IsReadOnly, false, !call.IsReadOnly, () =>
                {
                    return ExecuteProtectedOperationCall(call, body);
                });
            }
            catch (ThreadAbortException ex)
            {
                if (ThreadWrapper.IsIllegalAbort(ex))
                {
                    ThreadWrapper.ResetAbort();
                    Errors.FatalError("Using Thread.Abort is not allowed when using the Tasking library.");
                }

                Tracer.Trace("Protected operation " + call + " aborted");

                return default(RetT);
            }
        }


        /// <summary>
        /// Execute a protected operation
        /// </summary>
        /// <typeparam name="RetT">The type of the protected operation's result.</typeparam>
        /// <param name="call">The protected method call.</param>
        /// <param name="body">The body of the protected operation.</param>
        /// <returns>The result of the protected operation</returns>
        private static RetT ExecuteProtectedOperationCall<RetT>(SynchronizedMethod call, Func<RetT> body)
        {
            ThreadWrapper currentTask = ThreadWrapper.CurrentThread;

            try
            {
                currentTask.PushCall(call);
                return body();
            }
            finally
            {
                currentTask.PopCall();
            }
        }


        /// <summary>
        /// Perform a synchronous call of an entry or protected procedure or function.
        /// </summary>
        /// <typeparam name="RetT">The type of the call result.</typeparam>
        /// <typeparam name="ExecuteDelegateT">A delegate type representing the call's execution profile.</typeparam>
        /// <param name="call">The call.</param>
        /// <returns></returns>
        internal override RetT SynchronousCall<RetT, ExecuteDelegateT>(MethodCall call)
        {
            if (call.Target.IsEntry)
            {
                SynchronousEntryCall(call);
                return ((CallContext<RetT>)call.Context).Result;
            }

            return ProtectedOperationCall(call.TargetInstance, () =>
            {
                var target = (IExecutable<RetT, ExecuteDelegateT>)call.TargetInstance;
                return target.Execute(call.Context, target.ProtectedBody);
            });
        }


        /// <summary>
        /// Perform a call on an entry.
        /// </summary>
        /// <param name="call">The entry call to be performed</param>
        /// <param name="tryOnly">if set to <c>true</c> perform the call only if it can be accepted immediately.</param>
        internal override void EntryCall(MethodCall call, bool tryOnly)
        {
            // Make sure that the caller's current priority does not exceed the ceiling priority
            if (call.Thread.CurrentPriority > Priority)
                throw Errors.PriorityCeilingExceeded(call.Thread, call.Target);

            // Make sure that the entry has been initialized
            if (call.Target.AcceptableCalls.Count == 0)
                throw Errors.UninitializedProtectedOperation(call.Target);

            if (tryOnly)
                // {
                ProtectedAction<Void>("TRY ENTRY CALL", false, true, false, () =>
                {
                    if (_protectedLock.IsWriteLockHeld)
                    {
                        PendingCalls.Add(call);
                        ServiceQueues();
                        if (!call.Finished)
                            PendingCalls.Remove(call);
                    }

                    return null;
                });
            // }
            else
                ProtectedAction<Void>("ENTRY CALL", false, false, true, () =>
                {
                    PendingCalls.Add(call);
                    return null;
                });
        }


        /// <summary>
        /// Broadcasts a ProgramError exception in all entry calls waiting for execution.
        /// </summary>
        /// <param name="ex">The exception which triggered the broadcast.</param>
        private void BroadcastProgramError(Exception ex)
        {
            Tracer.Trace("Broadcasting ProgramError due to an exception within a protected action");

            foreach (MethodCall call in PendingCalls.ToArray())
            {
                PendingCalls.Remove(call);

                // Cancel alternative part of call (i.e. abortable part or timeout) if any
                call.CancelSelectiveCallAlternative();

                using (call.CallLock.Locked())
                {
                    // Simulate calls beeing accepted and resulting in an TaskingError exception
                    call.Context.Exception = Errors.BarrierEvaluationThrewException(this, ex);
                    Finish(call, true);
                }
            }
        }


        /// <summary>
        /// Services the queues, i.e. process all entry calls which can be accepted.
        /// </summary>
        private void ServiceQueues()
        {
            RendezVous rendezVous;

            while (true)
            {
                try
                {
                    rendezVous = FirstRendezVous();

                    if (rendezVous == null)
                        return;

                    PendingCalls.Remove(rendezVous.Call);
                    rendezVous.Call.CallLock.GetLock();
                    rendezVous.Call.CancelSelectiveCallAlternative();
                }
                catch (Exception ex)
                {
                    // LRM 9.5.3 (7) : If the evaluation of the condition propagates an exception,
                    // the exception Program_Error is propagated to all current callers of all
                    // entries of the protected object
                    BroadcastProgramError(ex);
                    return;
                }

                rendezVous.Execute(null);
                rendezVous.Call.CallLock.ReleaseLock();
            }
        }


        /// <summary>
        /// Finishes a synchronized method call.
        /// </summary>
        /// <param name="call">The call to  finish.</param>
        /// <param name="signalCaller">When <c>true</c> wake up the caller thread</param>
        internal override void Finish(MethodCall call, bool signalCaller)
        {
            DoFinish(call, signalCaller);
        }


        /// <summary>
        /// Cancel a pending call to a synchronized operation
        /// </summary>
        /// <param name="call">The call to cancel</param>
        /// <param name="signalCaller">When <c>true</c> wake up the caller thread</param>
        internal override void Cancel(MethodCall call, bool signalCaller)
        {
            // Make sure this instance has not been disposed
            if (Disposed)
                Errors.ProtectedHasBeenDisposed(this);

            if (call.Finished)
                return;

            Tracer.Trace("Cancelling entry call " + call);

            ProtectedAction<Void>("CALL CANCELLATION", false, false, true, () =>
            {
                Finish(call, signalCaller);
                return null;
            });
        }


        /// <summary>
        /// Execute a protected action
        /// </summary>
        /// <typeparam name="RetT">The type of the action's result.</typeparam>
        /// <param name="actionName">The name of the action.</param>
        /// <param name="readOnly">if set to <c>true</c> the action is a read-only action.</param>
        /// <param name="tryOnly">if set to <c>true</c>, try to execute the action but return if the action can't be executed immediately.</param>
        /// <param name="serviceQueues">if set to <c>true</c> service entry queues after the action has been executed.</param>
        /// <param name="operation">The protected action to execute.</param>
        /// <returns></returns>
        private RetT ProtectedAction<RetT>(string actionName, bool readOnly, bool tryOnly, bool serviceQueues, Func<RetT> operation)
        {
            using (ThreadWrapper.CurrentThread.NoAbort())
            {
                Tracer.Trace(string.Format("protected action {0} started on {1}", actionName, this));
                try
                {
                    _protectedLock.EnterLock(readOnly, tryOnly);

                    // Make sure this instance has not been disposed
                    if (Disposed)
                        Errors.ProtectedHasBeenDisposed(this);

                    return operation();
                }
                finally
                {
                    try
                    {
                        if (serviceQueues && _protectedLock.IsWriteLockHeld)
                            ServiceQueues();
                    }
                    finally
                    {
                        _protectedLock.ExitLock();
                        Tracer.Trace(string.Format("protected action {0} finished on {1}", actionName, this));
                    }
                }
            }
        }


        /// <summary>
        /// Requeues an entry call to a new entry call.
        /// This method is executed within the context of the task/protected type declaring the entry
        /// onto which the call is requeued.
        /// </summary>
        /// <param name="call">The call to be requeued</param>
        /// <param name="requeued">The entry to which the call is requeued</param>
        /// <param name="internalRequeue">Indicate whether the requeue is an internal requeue or an external requeue</param>
        /// <returns></returns>
        internal override MethodAccept Requeue(MethodCall call, MethodCall requeued, bool internalRequeue)
        {
            // Make sure this instance has not been disposed
            if (Disposed)
                Errors.ProtectedHasBeenDisposed(this);

            if (internalRequeue)
            {
                call.Requeued = requeued;
                PendingCalls.Add(call);
            }
            else
            {
                // Prevent External requeue during a protected action
                if (call.Thread.RecursiveCall(this))
                    throw Errors.PotentiallyBlockingOperationNotAllowed("External requeue from protected action");

                call.Requeued = requeued;

                ProtectedAction<Void>("REQUEUE", false, false, true, () =>
                {
                    PendingCalls.Add(call);
                    return null;
                });
            }

            return null;
        }


        /// <summary>
        /// Signal that a call has had it's caller task priority lowered
        /// </summary>
        /// <param name="call">The call for which the priority has been lowered</param>
        internal override void PriorityLowered(MethodCall call)
        {
            // Make sure this instance has not been disposed
            if (Disposed)
                Errors.ProtectedHasBeenDisposed(this);

            PendingCalls.PriorityLowered(call);

            ProtectedAction<Void>("PRIORITY LOWER", false, true, false, () =>
            {
                if (!call.Finished)
                    PendingCalls.PriorityLowered(call);
                return null;
            });
        }


        /// <summary>
        /// Signal that a call has had it's caller task priority raised
        /// </summary>
        /// <param name="call">The call for which the priority has been raised</param>
        internal override void PriorityRaised(MethodCall call)
        {
            // Make sure this instance has not been disposed
            if (Disposed)
                Errors.ProtectedHasBeenDisposed(this);

            ProtectedAction<Void>("PRIORITY RAISE", false, true, false, () =>
            {
                if (!call.Finished)
                    PendingCalls.PriorityRaised(call);
                return null;
            });
        }


        /// <summary>
        /// Checks that the current method is executed directly from this instance's constructor.
        /// </summary>
        private void CheckIsInConstructor()
        {
            StackTrace trace = new StackTrace();
            StackFrame frame = trace.GetFrame(3);
            MethodBase method = frame.GetMethod();

            if (method.Name != ".ctor" || method.IsStatic)
                throw Errors.InitializationNotInConstructor(this);
        }


        /// <summary>
        /// Declare a protected entry family
        /// </summary>
        /// <typeparam name="IndexT">The type of the family's index.</typeparam>
        /// <typeparam name="ExecuteDelegateT">A delegate type defining the family's execution profile.</typeparam>
        /// <param name="family">The family.</param>
        /// <param name="barrier">The barrier.</param>
        /// <param name="body">The body.</param>
        private void ProtectedFamily<IndexT, ExecuteDelegateT>(IExecutable<ExecuteDelegateT> family, Func<IndexT, bool> barrier, ExecuteDelegateT body)
            where ExecuteDelegateT : class
        {
            CheckIsInConstructor();

            if (family == null)
                throw Errors.EntryIsNull("Entry");

            if (family.Family.Owner != this)
                throw Errors.NotAMember(this, (SynchronizedMethod)family);

            //if (barrier == null)
            //    throw Errors.BarrierIsNull();

            if (body == null)
                throw Errors.ProtectedBodyIsNull(this, (SynchronizedMethod)family);

            var accept = (MethodAccept<ExecuteDelegateT>)(family.CreateAccept());
            ((IGuardable<IndexT>)accept).IndexedBarrier = barrier;
            accept.Body = body;
            accept.Target.AcceptableCalls.Add(accept);
        }


        /// <summary>
        /// Declare a protected entry family
        /// </summary>
        /// <typeparam name="ExecuteDelegateT">A delegate type defining the family's execution profile.</typeparam>
        /// <param name="family">The family.</param>
        /// <param name="barrier">The barrier.</param>
        /// <param name="body">The body.</param>
        private void ProtectedFamily<ExecuteDelegateT>(IExecutable<ExecuteDelegateT> family, Func<bool> barrier, ExecuteDelegateT body)
            where ExecuteDelegateT : class
        {
            CheckIsInConstructor();

            if (family == null)
                throw Errors.EntryIsNull("Entry");

            if (family.Family.Owner != this)
                throw Errors.NotAMember(this, (SynchronizedMethod)family);

            if (body == null)
                throw Errors.ProtectedBodyIsNull(this, (SynchronizedMethod)family);

            var accept = (MethodAccept<ExecuteDelegateT>)(family.CreateAccept());
            ((IGuardable)accept).Barrier = barrier;
            accept.Body = body;
            accept.Target.AcceptableCalls.Add(accept);
        }


        /// <summary>
        /// Declare a protected entry
        /// </summary>
        /// <typeparam name="CallDelegateT">A delegate type defining the entry's calling profile.</typeparam>
        /// <param name="entry">The entry</param>
        /// <param name="barrier">The barrier.</param>
        /// <param name="body">The body.</param>
        private void ProtectedEntry<CallDelegateT>(CallDelegateT entry, Func<bool> barrier, CallDelegateT body)
            where CallDelegateT : class
        {
            CheckIsInConstructor();

            if (entry == null)
                throw Errors.EntryIsNull("Entry");

            // ReSharper disable PossibleNullReferenceException
            var target = (entry as MulticastDelegate).Target;
            // ReSharper restore PossibleNullReferenceException

            var method = target as SynchronizedMethod;

            if (method == null)
                throw Errors.NotASynchronizedMethod();

            if (method.Family != method)
                throw Errors.FamilyMemberNotAllowed(this, method);

            if (method.Owner != this)
                throw Errors.NotAMember(this, method);

            if (!method.IsEntry)
                throw Errors.DeclarationConflictsWithAttribute(method);

            if (body == null)
                throw Errors.ProtectedBodyIsNull(this, method.Family);

            var executable = (IExecutable<CallDelegateT>)method;
            var context = (MethodAccept<CallDelegateT>)executable.CreateAccept();

            context.Barrier = barrier;
            context.Body = body;
            context.Target.AcceptableCalls.Add(context);
        }


        /// <summary>
        /// Declare a protected procedure or function
        /// </summary>
        /// <typeparam name="CallDelegateT">A delegate type defining the proc/func's calling profile.</typeparam>
        /// <param name="operation">The proc/func</param>
        /// <param name="isReadOnly">Define whether the method is a read-only method</param>
        /// <param name="body">The body.</param>
        private void ProtectedMethod<CallDelegateT>(CallDelegateT operation, bool isReadOnly, CallDelegateT body)
            where CallDelegateT : class
        {
            CheckIsInConstructor();

            if (operation == null)
                throw Errors.EntryIsNull("Read[Only/Write]Operation");

            // ReSharper disable PossibleNullReferenceException
            var target = (operation as MulticastDelegate).Target;
            // ReSharper restore PossibleNullReferenceException

            var method = target as SynchronizedMethod;

            if (method == null)
                throw Errors.NotASynchronizedMethod();

            if (method.Family != method)
                throw Errors.FamilyMemberNotAllowed(this, method);

            if (method.Owner != this)
                throw Errors.NotAMember(this, method);

            if (method.IsEntry)
                throw Errors.DeclarationConflictsWithAttribute(method);

            if (method.IsReadOnly && !isReadOnly)
                throw Errors.DeclarationConflictsWithAttribute(method);

            if (!method.IsReadOnly && isReadOnly)
                throw Errors.DeclarationConflictsWithAttribute(method);

            if (body == null)
                throw Errors.ProtectedBodyIsNull(this, method);

            // Reset any initialization which has been performed in the parent class Body
            method.AcceptableCalls.Clear();
            ((IExecutable<CallDelegateT>)method).ProtectedBody = body;
        }

        #endregion

    }

}
